/**********************************************************************
*  Copyright (c) 2007 - Victor Jacobs - victor.jacobs@gmail.com
*
*  Permission is hereby granted, free of charge, to any person
*  obtaining a copy of this software and associated documentation
*  files (the "Software"), to deal in the Software without
*  restriction, including without limitation the rights to use,
*  copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the
*  Software is furnished to do so, subject to the following
*  conditions:
*
*  The above copyright notice and this permission notice shall be
*  included in all copies or substantial portions of the Software.
*
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
*  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
*  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*  OTHER DEALINGS IN THE SOFTWARE.
**********************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
/**********************************************************************
*  Copyright (c) 2007 - Victor Jacobs - victor.jacobs@gmail.com
*
*  Permission is hereby granted, free of charge, to any person
*  obtaining a copy of this software and associated documentation
*  files (the "Software"), to deal in the Software without
*  restriction, including without limitation the rights to use,
*  copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the
*  Software is furnished to do so, subject to the following
*  conditions:
*
*  The above copyright notice and this permission notice shall be
*  included in all copies or substantial portions of the Software.
*
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
*  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
*  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*  OTHER DEALINGS IN THE SOFTWARE.
**********************************************************************/

using System.Text;
using System.Windows.Forms;
using System.Collections;
using Vj.MyMath;

namespace Vj.GATool.Controls
{
    public enum PlotResolution
    {
        Low, Average, High
    }

    public partial class Plot3DPanel : UserControl
    {
        #region Data Members

        private Graphics myGraphic;

        private AFunction function3D;

        PlotResolution myResolution;

        private bool doDrawFunction;
        private bool doDrawAxis;
        private bool doDrawData;

        private Vector xAxisM, xAxisP;
        private Vector yAxisM, yAxisP;
        private Vector zAxisM, zAxisP;

        private Vector[] boxData;

        private Vector[,] meshData;
        private int meshSize;
        private int meshDist;

        private ArrayList dataList;

        private MatrixDouble mainTransformationMatrix;

        #endregion

        #region Properties

        public AFunction Function3D
        {
            get { return function3D; }
            set
            {
                function3D = value;
                DefineMesh(function3D);
            }
        }

        public bool DoDrawFunction
        {
            get { return doDrawFunction; }
            set
            {
                doDrawFunction = value;
                this.Refresh();
            }
        }
        public bool DoDrawAxis
        {
            get { return doDrawAxis; }
            set
            {
                doDrawAxis = value;
                this.Refresh();
            }
        }
        public bool DoDrawData
        {
            get { return doDrawData; }
            set
            {
                doDrawData = value;
                this.Refresh();
            }
        }
        private double xRotAngle;
        public double XRotAngle
        {
            set
            {
                xRotAngle = value;
                CalculateMainTransformationMatrix();
                Refresh();
            }
            get { return xRotAngle; }
        }

        private double yRotAngle;
        public double YRotAngle
        {
            set
            {
                yRotAngle = value;
                CalculateMainTransformationMatrix();
                Refresh();
            }
            get { return yRotAngle; }
        }

        private double zRotAngle;
        public double ZRotAngle
        {
            set
            {
                zRotAngle = value;
                CalculateMainTransformationMatrix();
                Refresh();
            }
            get { return zRotAngle; }
        }

        private double xTransFactor;
        public double XTransFactor
        {
            set
            {
                xTransFactor = value;
                CalculateMainTransformationMatrix();
                Refresh();
            }
            get { return xTransFactor; }
        }

        private double yTransFactor;
        public double YTransFactor
        {
            set
            {
                yTransFactor = value;
                CalculateMainTransformationMatrix();
                Refresh();
            }
            get { return yTransFactor; }
        }

        private double zTransFactor;
        public double ZTransFactor
        {
            set
            {
                zTransFactor = value;
                CalculateMainTransformationMatrix();
                Refresh();
            }
            get { return zTransFactor; }
        }

        private double scaleFactor;
        public double ScaleFactor
        {
            set
            {
                scaleFactor = value;
                CalculateMainTransformationMatrix();
                Refresh();
            }
            get { return scaleFactor; }
        }

        public void SetHighResolution()
        {
            myResolution = PlotResolution.High;
            DefineMesh(function3D);
            Refresh();
        }

        public void SetAverageResolution()
        {
            myResolution = PlotResolution.Average;
            DefineMesh(function3D);
            Refresh();
        }

        public void SetLowResolution()
        {
            myResolution = PlotResolution.Low;
            DefineMesh(function3D);
            Refresh();
        }



        #endregion

        public Plot3DPanel(AFunction func)
        {
            InitializeComponent();

            doDrawAxis = true;
            doDrawFunction = true;
            doDrawData = true;

            function3D = func;

            scaleFactor = 1.0;

            myResolution = PlotResolution.Average;

            DefineMesh(function3D);

            DefineAxis(125.0);

            DefineBox(110.0);

            dataList = new ArrayList();

            mainTransformationMatrix = MatrixDouble.GetIdentityMatrix();
        }

        private void CalculateMainTransformationMatrix()
        {
            MatrixDouble xRot = MatrixDouble.GetRotMatrix(1, 0, 0, xRotAngle);
            MatrixDouble yRot = MatrixDouble.GetRotMatrix(0, 1, 0, yRotAngle);
            mainTransformationMatrix = xRot * yRot;
        }

        private void Plot3DPanel_Paint(object sender, PaintEventArgs e)
        {
            Bitmap drawing = null;
            drawing = new Bitmap(this.Width, this.Height, e.Graphics);
            myGraphic = Graphics.FromImage(drawing);

            myGraphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

            //Draw2DFunction(new Pen(Color.White, 1.0f), function3D);

            if (doDrawAxis)
                DrawAxis(new Pen(Color.Sienna, 2.0f));

            if (doDrawFunction)
                DrawFunction(new Pen(Color.DarkSlateBlue, 1.0f), function3D);

            if (doDrawData)
                DrawData();

            e.Graphics.DrawImageUnscaled(drawing, 0, 0);
            myGraphic.Dispose();

           

        }



        #region Define Axis, Data and Plot Mesh
        public void SetData(ArrayList data)
        {
            for (int i = 0; i < data.Count; i++)
            {
                Vector inVector = (Vector)data[i];
                Vector newVector = new Vector(inVector.X, inVector.Y, inVector.Z, 1.0);
                dataList.Add(newVector);
            }
            this.Refresh();
        }

        public void ClearData()
        {
            dataList.Clear();
            this.Refresh();
        }

        private void DefineAxis(double axisLength)
        {
            xAxisP = new Vector(axisLength, 0.0, 0.0, 1);
            yAxisP = new Vector(0.0, axisLength, 0.0, 1);
            zAxisP = new Vector(0.0, 0.0, axisLength, 1);

            xAxisM = new Vector(-axisLength, 0.0, 0.0, 1);
            yAxisM = new Vector(0.0, -axisLength, 0.0, 1);
            zAxisM = new Vector(0.0, 0.0, -axisLength, 1);
        }

        private void DefineMesh(AFunction aFunction)
        {
            //int plotWidth = 100;
            int plotWidth = 128;

            if (myResolution == PlotResolution.Low)
            {
                meshDist = 25;
                meshSize = 2 * plotWidth / meshDist + 1;
            }
            else if (myResolution == PlotResolution.Average)
            {
                meshDist = 8;
                meshSize = 2 * plotWidth / meshDist + 1;
            }
            else if (myResolution == PlotResolution.High)
            {
                meshDist = 5;
                meshSize = 2 * plotWidth / meshDist + 1;
            }

            meshData = new Vector[meshSize, meshSize];

            for (int i = 0; i < meshSize; i++)
            {
                for (int j = 0; j < meshSize; j++)
                {
                    double x = -((double)i * meshDist) + plotWidth;
                    double z = -((double)j * meshDist) + plotWidth;
                    double y = aFunction(new double[] { x, z });

                    meshData[i, j] = new Vector(x, y, z, 1);
                }
            }
        }

        public void DefineBox(double size)
        {
            boxData = new Vector[8];
            boxData[0] = new Vector(1.0 * size, 1.0 * size, 1.0 * size, 1.0);
            boxData[1] = new Vector(1.0 * size, -1.0 * size, 1.0 * size, 1.0);
            boxData[2] = new Vector(-1.0 * size, -1.0 * size, 1.0 * size, 1.0);
            boxData[3] = new Vector(-1.0 * size, 1.0 * size, 1.0 * size, 1.0);

            boxData[4] = new Vector(1.0 * size, 1.0 * size, -1.0 * size, 1.0);
            boxData[5] = new Vector(1.0 * size, -1.0 * size, -1.0 * size, 1.0);
            boxData[6] = new Vector(-1.0 * size, -1.0 * size, -1.0 * size, 1.0);
            boxData[7] = new Vector(-1.0 * size, 1.0 * size, -1.0 * size, 1.0);

        }

        #endregion


        #region Draw Function Plot, Axis, Data
        public void DrawFunction(Pen pen, AFunction aFunction)
        {
            for (int i = 0; i < meshSize - 1; i++)
            {
                for (int j = 0; j < meshSize - 1; j++)
                {
                    Vector v1 = mainTransformationMatrix * meshData[i, j];
                    Vector v2 = mainTransformationMatrix * meshData[i + 1, j];
                    Vector v3 = mainTransformationMatrix * meshData[i, j + 1];

                    DrawLine(pen, v1[0], v1[1], v2[0], v2[1]);
                    DrawLine(pen, v1[0], v1[1], v3[0], v3[1]);

                    if (j == meshSize - 2)
                    {
                        Vector v4 = mainTransformationMatrix * meshData[i + 1, j + 1];
                        DrawLine(pen, v3[0], v3[1], v4[0], v4[1]);
                    }
                    if (i == meshSize - 2)
                    {
                        Vector v4 = mainTransformationMatrix * meshData[i + 1, j + 1];
                        DrawLine(pen, v2[0], v2[1], v4[0], v4[1]);
                    }
                }
            }
        }


        //public void Draw2DFunction(Pen pen, AFunction aFunction)
        //{
        //    for (int i = 0; i < meshSize - 1; i++)
        //    {
        //        int j = 20;

        //        Vector v1 = mainTransformationMatrix * meshData[i, j];
        //        Vector v2 = mainTransformationMatrix * meshData[i + 1, j];

        //        DrawLine(pen, v1[0], v1[1], v2[0], v2[1]);
        //    }
        //}



        private void DrawData()
        {
            for (int i = 0; i < dataList.Count; i++)
            {
                Vector tmp = (Vector)dataList[i];
                tmp = mainTransformationMatrix * tmp;

                if (i == 0)
                    DrawDataPoint(Pens.Green, 2.0f, (float)tmp[0], (float)tmp[1]);
                else if (i == dataList.Count - 1)
                    DrawDataPoint(Pens.Red, 2.0f, (float)tmp[0], (float)tmp[1]);
                else
                    DrawDataPoint(Pens.Wheat, 1.0f, (float)tmp[0], (float)tmp[1]);
            }
        }

        public void DrawAxis(Pen pen)
        {
            DefineAxis(125.0);
            xAxisP = mainTransformationMatrix * xAxisP;
            yAxisP = mainTransformationMatrix * yAxisP;
            zAxisP = mainTransformationMatrix * zAxisP;

            xAxisM = mainTransformationMatrix * xAxisM;
            yAxisM = mainTransformationMatrix * yAxisM;
            zAxisM = mainTransformationMatrix * zAxisM;

            DrawLine(pen, xAxisP[0], xAxisP[1], xAxisM[0], xAxisM[1]);
            DrawLine(pen, yAxisP[0], yAxisP[1], yAxisM[0], yAxisM[1]);
            DrawLine(pen, zAxisP[0], zAxisP[1], zAxisM[0], zAxisM[1]);
        }

        #endregion


        #region Point and Line Drawing Functions

        protected void DrawLine(Pen aPen, float x0, float y0, float x1, float y1)
        {
            float w = (float)this.Width;
            float h = (float)this.Height;
            myGraphic.DrawLine(aPen, x0 + (w / 2.0f), (h / 2.0f) - y0,
                x1 + (w / 2.0f), (h / 2.0f) - y1);
        }

        protected void DrawLine(Pen aPen, double x0, double y0, double x1, double y1)
        {
            double w = (double)this.Width;
            double h = (double)this.Height;
            myGraphic.DrawLine(aPen, (float)(x0 + (w / 2.0)), (float)((h / 2.0) - y0),
                (float)(x1 + (w / 2.0)), (float)((h / 2.0) - y1));
        }

        public void DrawDataPoint(Pen pen, float size, float x, float y)
        {
            int w = this.Width;
            int h = this.Height;

            SolidBrush myBrush = new SolidBrush(pen.Color);

            myGraphic.FillEllipse(myBrush, (float)x + w / 2 - size / 2, (float)h / 2 - y - size / 2,
                2 * size, 2 * size);

            //myGraphic.DrawEllipse(pen, (float)x + w / 2 - size / 2, (float)h / 2 - y - size / 2, 
            //    2*size, 2*size);
        }

        #endregion


        #region MouseMovement
        private Point oldLoc;
        private Point orginalLoc;
        private void Plot3DPanel_MouseDown(object sender, MouseEventArgs e)
        {
            orginalLoc = e.Location;
        }

        private void Plot3DPanel_MouseUp(object sender, MouseEventArgs e)
        {
            orginalLoc = e.Location;
        }

        private void Plot3DPanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && e.Location != oldLoc)
            {
                oldLoc = e.Location;
                YRotAngle = (double)(orginalLoc.X - e.X);
                XRotAngle = (double)(orginalLoc.Y - e.Y);
                Refresh();
            }
        }

        #endregion

    }
}

